/**
 * The contents of this file are subject to the license and copyright detailed in the LICENSE and NOTICE files at the root of the source tree and available online at
 * http://www.askosi.org/license.html
 */
package be.destin.skos.view;

import java.io.IOException;
import java.io.Writer;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.Set;
import java.util.TreeMap;

import javax.servlet.http.HttpServletRequest;

import org.apache.log4j.Logger;

import be.destin.rdf.changes.Status;
import be.destin.skos.core.Concept;
import be.destin.skos.core.ConceptScheme;
import be.destin.skos.core.LinkedConcept;
import be.destin.skos.core.LocalLinkedConcept;
import be.destin.skos.core.SchemeInterface;
import be.destin.skos.core.SkosManager;
import be.destin.skos.core.TermList;
import be.destin.skos.html.SkosHtmlManager;
import be.destin.skos.html.SkosHtmlTable;
import be.destin.skos.xml.XMLUtil;
import be.destin.util.Util;

public class SkosViewManager {

	/** log4j category */
	private static Logger log = Logger.getLogger(SkosViewManager.class);

	public static HashSet<SkosView> openView (SkosHtmlManager skosHtmlManager,Set<String> schemes, SkosViewType viewType, String userLanguage, String filter, boolean strictLanguage) {
		if (skosHtmlManager == null) return null;
		HashSet<SkosView> result = new HashSet<SkosView>();
		for (String aScheme: schemes) {
			SkosView aView = openView(skosHtmlManager, aScheme, viewType, userLanguage, strictLanguage, filter);
			if (aView != null) result.add(aView);
		}
		return result;
	}

	public static SkosView openView (SkosHtmlManager skosHtmlManager,String aScheme, SkosViewType viewType, String userLanguage, boolean strictLanguage, String filter) {
		if (skosHtmlManager == null) return null;
		SkosView aView = new SkosView(skosHtmlManager, aScheme, viewType, userLanguage, strictLanguage);
		if (filter != null && filter.length() > 0) aView.setFilter(filter);
		return aView;
	}

	/**
	 * Converts a XML Vocabulary to a HTML tree
	 * 
	 * @param vocabularies
	 *            A hashtable with all the XML taxonomies/vocabularies loaded as
	 *            values
	 * @param xslt
	 *            the filename of the stylesheet to apply the XML taxonomies
	 * @param allowMultipleSelection
	 *            include checkboxes next to the taxonomy terms
	 * @param contextPath
	 *            The context path
	 * @return the HTML that represents the vocabularies
	 */
	public static void renderVocabulariesAsHTML(HttpServletRequest request, Writer out, HashSet<SkosView> vocabularies,
	        String xslt, String searchIndex, String application, boolean allowMultipleSelection)
	{
	    for (SkosView controlledVocabularyXML : vocabularies) {
	        renderVocabularyAsHTML(request, out, controlledVocabularyXML, xslt,
	                searchIndex, application, allowMultipleSelection);
	    }
	}

	/**
	 * Renders a taxonomy as HTML by applying a stylesheet.
	 * 
	 * @param vocabulary
	 *            The XML document representing a taxonomy
	 * @param controlledVocabulary2HtmlXSLT
	 *            The filename of the stylesheet that converts the taxonomy to
	 *            HTML
	 * @param allowMultipleSelection
	 *            include checkboxes next to the taxonomy terms
	 * @param contextPath
	 *            The context path
	 * @return the provided taxonomy as HTML.
	 */
	public static void renderVocabularyAsHTML(HttpServletRequest request, Writer out, SkosView vocabulary,
	        String controlledVocabulary2HtmlXSLT,
	        String searchIndex,
	        String application,
	        boolean allowMultipleSelection)
	{
		if (vocabulary == null)
	        return;
	
	    try
	    {
	    	String contextPath = request.getContextPath();
	        Hashtable<String,String> parameters = new Hashtable<String,String>();
	        if (searchIndex == null || searchIndex.length()==0) searchIndex = "keyword";
	        parameters.put("searchIndex", searchIndex);
	        parameters.put("application", application);
	        parameters.put("allowMultipleSelection",
	                allowMultipleSelection ? "yes" : "no");
	        parameters.put("contextPath", contextPath);
	        XMLUtil.transformObject2Writer(
	                vocabulary.getSkosHtmlManager().getSkosManager().getConfigDirectory(), controlledVocabulary2HtmlXSLT, parameters, vocabulary, out);
	    }
	    catch (Exception e)
	    {
	        log.error("Transform exception: " + e.toString());
	        e.printStackTrace();
	    }
	    log.info("Invoked vocabulary: " + vocabulary.toString() + ", XSLT:" +controlledVocabulary2HtmlXSLT);
	}

	/**
	 * Renders a taxonomy as JSON
	 */
	public static void renderVocabularyAsJSON(Writer out, SkosView vocabulary)
	{
		if (vocabulary == null)
	        return;
	
	    try
	    {
	        XMLUtil.object2JSON(vocabulary, out);
	    }
	    catch (Exception e)
	    {
	        log.error("JSON Render exception: " + e.toString());
	        e.printStackTrace();
	    }
	    log.info("JSONize vocabulary: " + vocabulary.toString() );
	}

	private static void narrowerToHtml (SkosHtmlManager skosHtmlManager, Writer out, SkosView vocabulary,
			LinkedList<ViewConcept> narrowers,
			HashSet<String> written,
			ViewConcept[] uppers,
			int depth)

	throws IOException
	{
		if (depth >= Concept.maxDepth) {
			Concept anEntry = uppers[Concept.maxDepth-1].getConcept();
			String message = anEntry.getScheme_About()+": is too deep. Depth="+depth;
			log.error(message);
			anEntry.addError(message);		
		}
		else if (narrowers != null && !narrowers.isEmpty()) {
			SchemeInterface currScheme = skosHtmlManager.getSkosManager().getScheme(vocabulary.getAbout());
			out.write("<ul class=\"SkosView\">");
			String pivot = vocabulary.getPivot();
			if (pivot==null) pivot = "";
			else if (!pivot.isEmpty()) {
				pivot = currScheme.getAbout_About(pivot);
			}
			for (ViewConcept aViewConcept: narrowers) {
				Concept anEntry = aViewConcept.getConcept();
				if (anEntry == null) continue;
				boolean looping = false;
				for (int i=0; i < depth; i++) {
					if (anEntry.getScheme_About().equals(uppers[i].getScheme_About())) {
						String message = anEntry.getScheme_About()+": is in a hierarchy loop. Depth="+i+" and "+depth;
						log.error(message);
						anEntry.addError(message);
						looping = true;
						break;
					}
				}
				if (looping) continue;
				out.write("<li>");
				boolean isPivot = pivot.equals(aViewConcept.getScheme_About());
				if (isPivot) out.write("<span class=\"SkosViewPivot\">");
				if (written.contains(aViewConcept.getScheme_About())) {
					ConceptScheme aScheme = anEntry.getInScheme();
					String icon = aScheme.getIconAdapted(skosHtmlManager);
					out.write
					      ( "<a href=\"#"
							+aViewConcept.getScheme_About()
							+"\" onmouseover=\"document.getElementsByName('"
							+aViewConcept.getScheme_About()
							+"')[0].style.backgroundColor='LightGrey';\" onmouseout=\"document.getElementsByName('"
							+aViewConcept.getScheme_About()
							+"')[0].style.backgroundColor='white';\">"
							+ ( icon != null
								? skosHtmlManager.toHtmlImage(icon, aViewConcept.getScheme_About(), false)
								: "")
							+ "<u>"
							+ aViewConcept.getPrefLabel()
							+ "</u> &uarr;</a>" );					
					if (isPivot) out.write("</span>");
				} else {
					// out.write("<a name=\""+aViewConcept.getAbout()+"\"/>"); PLUS NECESSAIRE, a �t� ajout� dans le lien sous-jacent
					out.write(anEntry.toHtmlLabelUsage(skosHtmlManager));
					written.add(aViewConcept.getScheme_About());
					if (isPivot) out.write("</span>");
					uppers[depth] = aViewConcept;
					narrowerToHtml(skosHtmlManager,out,vocabulary,aViewConcept.getNarrower(),written,uppers,depth+1);
				}
				out.write("</li>");
			}
			out.write("</ul>");
		}
	}

	public static void toHtmlOut(SkosHtmlManager skosHtmlManager, Writer out, SkosView vocabulary)
	{   HashSet<String> written = new HashSet<String>(); 
		if (vocabulary == null)
			return;
		ViewConcept [] uppers = new ViewConcept[Concept.maxDepth];
		try {
			narrowerToHtml(skosHtmlManager,out,vocabulary,vocabulary.getNarrower(),written,uppers,0);
			log.info("Invoked vocabulary: " + vocabulary.toString());
		} catch (IOException e) {
			log.error("IO error while processing " + vocabulary.toString(), e);
		}
	}

	private static void narrowerToHtmlTable (Writer out, SkosView vocabulary,
			LinkedList<ViewConcept> narrowers,
			HashSet<String> written,
			SkosHtmlTable skosHtmlTable,
			boolean[] uppersRemains,
			boolean[] uppersHiddenBrothers,
			ViewConcept[] uppers,
			int depth)
	throws IOException
	{
		if (depth >= Concept.maxDepth) {
			Concept anEntry = uppers[Concept.maxDepth-1].getConcept();
			String message = anEntry.getScheme_About()+": is too deep. Depth="+depth;
			log.error(message);
			anEntry.addError(message);		
		}
		else if (narrowers != null && !narrowers.isEmpty()) {
			uppersHiddenBrothers[depth] = vocabulary.isSomeRejects();
			SkosHtmlManager skosHtmlManager = skosHtmlTable.getSkosHtmlManager();
			SchemeInterface currScheme = skosHtmlManager.getSkosManager().getScheme(vocabulary.getAbout());
			String pivot = vocabulary.getPivot();
			if (pivot==null) pivot = "";
			else if (!pivot.isEmpty()) {
				pivot = currScheme.getAbout_About(pivot);
			}

			TreeMap<String,ViewConcept> sorter = new TreeMap<String,ViewConcept>();
			for (ViewConcept aViewConcept: narrowers) {
				Concept anEntry = aViewConcept.getConcept();
				if (anEntry != null) {
					boolean noLoop = true;
					for (int i=0; i < depth; i++) {
						if (anEntry.getScheme_About().equals(uppers[i].getScheme_About())) {
							String message = anEntry.getScheme_About()+": is in a hierarchy loop. Depth="+i+" and "+depth;
							log.error(message);
							anEntry.addError(message);
							noLoop = false;
							break;
						}
					}
					if (noLoop) sorter.put(anEntry.getSortLabel(vocabulary.getLang()), aViewConcept );
				}
			}

			int remains = sorter.size();
			for (ViewConcept aViewConcept: sorter.values()) {
				remains--;
				Concept aConcept = aViewConcept.getConcept();
				if (aConcept == null) continue;
				ConceptScheme aScheme = aConcept.getInScheme();
				if (aScheme == null) continue;
				uppers[depth] = aViewConcept;
				uppersRemains[depth] = remains > 0;
				out.write("<tr class=\""+skosHtmlTable.nextRowClass()+"\">");
				out.write("<td class=\"SkosStatusConcept\">"
						+aConcept.toHtmlStatus(skosHtmlManager)
						+"</td>");
				out.write ("<td class=\"SkosDepthConcept\">");
				if (skosHtmlTable.isHierarchized()) {
					if (depth > 0) {
						out.write("<a href=\"#"
								   + uppers[depth-1].getScheme_About()
								   +  "\" title=\""
								   + uppers[depth-1].getPrefLabel()
								   + "\">");
						out.write(Integer.toString(depth+1));
						out.write("</a>");
					} else out.write('1'); 
				}
				out.write ("</td>");
				boolean isPivot = pivot.equals(aViewConcept.getScheme_About());
				LinkedList<ViewConcept> nextNarrowers = aViewConcept.getNarrower();
				LinkedList<String> NTs = aConcept.getAboutNarrower();
				if (skosHtmlTable.isColSpanning()) { // Table indented with columns
					for (int i=0; i < depth; i++) {
						out.write("<td class=\"SkosSpannedPrefix"
									+ (uppersRemains[i]
									        ?  ""
											: (uppersHiddenBrothers[i] ? "Partial" :"Last") )
									+ "\">&nbsp;</td>");
					}
					String icon = aConcept.getIconAdapted(skosHtmlManager);
					if (icon == null || icon.isEmpty()) {
						icon = aScheme.getIconAdapted(skosHtmlManager);
					}
					out.write("<td class=\"SkosSpannedTick"
								+ (uppersRemains[depth] ? "":"Last")
								+ (nextNarrowers != null && !nextNarrowers.isEmpty()
										? "Drilled"
										: (((NTs != null && !NTs.isEmpty())
												||(nextNarrowers != null && !nextNarrowers.isEmpty()))
											? "Develop" : "" ) )
								+ "\">"
								+ ( icon != null
										? skosHtmlManager.toHtmlImage(icon, aViewConcept.getScheme_About(), false)
										: "")
								+ "</td>");
					out.write("<td class=\"SkosSpanned"
							  +(isPivot? "Pivot" : "Concept")
							  +"\" colspan=\""+(Concept.maxDepth-depth)+"\">");
					if (written.contains(aViewConcept.getScheme_About())) {
						out.write
						( "<a href=\"#"
								+aViewConcept.getScheme_About()
								+"\" onmouseover=\"document.getElementsByName('"
								+aViewConcept.getScheme_About()
								+"')[0].style.backgroundColor='LightGrey';\" onmouseout=\"document.getElementsByName('"
								+aViewConcept.getScheme_About()
								+"')[0].style.backgroundColor='white';\">"
								+ "<u>"
								+ aViewConcept.getPrefLabel()
								+ "</u> &uarr;</a>" );					
					} else {
						// out.write("<a name=\""+aViewConcept.getAbout()+"\"/>"); PLUS NECESSAIRE, a �t� ajout� dans le lien sous-jacent
						out.write(aConcept.toHtmlLabel(skosHtmlManager,false));
					}
				} else { // Regular table
					out.write("<td class=\"SkosTableConcept"+depth+"\">");
					if (isPivot) out.write("<span class=\"SkosViewPivot\">");
					if (written.contains(aViewConcept.getScheme_About())) {
						String icon = aConcept.getIconAdapted(skosHtmlManager);
						if (icon == null || icon.isEmpty()) {
							icon = aScheme.getIconAdapted(skosHtmlManager);
						}
						out.write
						( "<a href=\"#"
								+aViewConcept.getScheme_About()
								+"\" onmouseover=\"document.getElementsByName('"
								+aViewConcept.getScheme_About()
								+"')[0].style.backgroundColor='LightGrey';\" onmouseout=\"document.getElementsByName('"
								+aViewConcept.getScheme_About()
								+"')[0].style.backgroundColor='white';\">"
								+ ( icon != null ? icon : "")
								+ "<u>"
								+ aViewConcept.getPrefLabel()
								+ "</u> &uarr;</a>" );					
					} else {
						// out.write("<a name=\""+aViewConcept.getAbout()+"\"/>"); PLUS NECESSAIRE, a �t� ajout� dans le lien sous-jacent
						out.write(aConcept.toHtmlLabel(skosHtmlManager,true));
					}
					if (isPivot) out.write("</span>");
				}
				out.write("</td>");
				out.write(skosHtmlTable.toHtmlColumns(aViewConcept.getConcept()));
				out.write("</tr>");
				skosHtmlTable.setLimit(skosHtmlTable.getLimit()-1);
				if ( ! written.contains(aViewConcept.getScheme_About())) {
					written.add(aViewConcept.getScheme_About());
					if (skosHtmlTable.getLimit() >= 0) {
						narrowerToHtmlTable(out,vocabulary,nextNarrowers,written, skosHtmlTable, uppersRemains,uppersHiddenBrothers, uppers, depth+1);
					}
				}
				if (skosHtmlTable.getLimit() < 0) break;
			}
		}
	}

	public static void toHtmlTableOut(Writer out, SkosView vocabulary, SkosHtmlTable skosHtmlTable)
	{   HashSet<String> written = new HashSet<String>(); 
		if (vocabulary == null)
			return;
		boolean[] uppersLast = new boolean[Concept.maxDepth];
		boolean[] uppersHiddenBrothers = new boolean[Concept.maxDepth];
		ViewConcept[] uppers = new ViewConcept[Concept.maxDepth];
		try {
			narrowerToHtmlTable(out,vocabulary,vocabulary.getNarrower(),written,skosHtmlTable,uppersLast,uppersHiddenBrothers, uppers,0);
			log.info("Invoked vocabulary: " + vocabulary.toString());
		} catch (IOException e) {
			log.error("IO error while processing " + vocabulary.toString(), e);
		}
	}

	public static LinkedList<ViewConcept> getConcepts(SkosView vocabulary) {
		if (vocabulary == null)
			return null;
		LinkedList<ViewConcept> result = new LinkedList<ViewConcept>();
		int total = vocabulary.addConceptList(vocabulary.getNarrower(),result);
		log.info("Invoked vocabulary: " + vocabulary.toString()+" = "+total+" concepts.");
		return result;
	}
	
	public static String toDotConceptHierarchy(SkosHtmlManager skosHtmlManager, SkosView skosView, ViewConcept viewConcept, LinkedList<String> nodes, boolean inContext) {
		StringBuffer result = new StringBuffer();
		Concept aConcept = viewConcept.getConcept();
		if (aConcept == null) return "";
		String thisAbout = aConcept.getScheme_About();
		if (!inContext && thisAbout.equals(skosView.getBroadContext())) {
			inContext = true;
		}
		if (inContext) {
			nodes.add(thisAbout);
			LinkedList<LinkedConcept> allBTMs = aConcept.getBroadMatch();
			if (allBTMs != null && !allBTMs.isEmpty()) {
				for (LinkedConcept btm : allBTMs) {
					result.append(btm.getAbout());
					nodes.add(btm.getAbout());
					result.append("->");
					result.append(thisAbout);
					result.append("[dir=both,arrowtail=odot,arrowhead=normal");
					result.append(Status.graphVizColor(btm.getCurrentStatus()));
					result.append(Status.graphVizLabel(btm.getRelationQualifier(),btm.getCurrentStatus()));
					result.append("];\n");
				}
			}
		}
		
		ConceptScheme aScheme = aConcept.getInScheme();
		LinkedList<ViewConcept> nts = viewConcept.getNarrower();
		int countNTs = 0;
		int countNTmatchs = 0;
		if (nts != null && !nts.isEmpty()) {
			LinkedList<LocalLinkedConcept> allNTs = aConcept.getNarrower();
			LinkedList<LinkedConcept> allNTmatchs = aConcept.getNarrowMatch();
			countNTs = allNTs.size();
			countNTmatchs = allNTmatchs.size();
			int viewnts = 0;
			int viewntmatchs = 0;
			for (ViewConcept vcNT : nts) {
				if (inContext) {
					String anAbout = vcNT.getScheme_About(); 
					LinkedConcept currNT = null;
					boolean local = true;
					for (LocalLinkedConcept aRel: allNTs) {
						if (anAbout.equals(aRel.getScheme_About(aScheme))) {
							currNT = aRel;
							viewnts++;
							break;
						}
					}
					if (currNT == null) {
						local = false;
						for (LinkedConcept aRel: allNTmatchs) {
							if (anAbout.equals(aRel.getAbout())) {
								currNT = aRel;
								viewntmatchs++;
								break;
							}
						}
					}
					if (currNT == null) continue;
					result.append(thisAbout);
					result.append("->");
					result.append(anAbout);
					result.append("[");
					if (!local) {
						result.append("arrowhead=odotnormal");
					} else {
						result.append("arrowhead=normal");
					}
					result.append(Status.graphVizColor(currNT.getCurrentStatus()));
					result.append(Status.graphVizLabel(currNT.getRelationQualifier(),currNT.getCurrentStatus()));
					result.append("];\n");
				}
				result.append(toDotConceptHierarchy(skosHtmlManager,skosView,vcNT, nodes, inContext));
			}
			if (inContext) {
				if (countNTs > viewnts) {
					result.append(thisAbout);
					result.append("->");
					result.append(thisAbout);
					result.append('_');
					result.append(countNTs-viewnts);
					result.append("[arrowhead=empty,style=dotted];\n");
					result.append(thisAbout);
					result.append('_');
					result.append(countNTs-viewnts);
					result.append("[shape=plaintext,style=solid,fontsize=10,label=\"");
					result.append(countNTs-viewnts);
					result.append("\"];\n");
				}
				if (countNTmatchs > viewntmatchs) {
					result.append(thisAbout);
					result.append("->");
					result.append(thisAbout);
					result.append('_');
					result.append(countNTs-viewnts);
					result.append("[arrowhead=odotnormal,style=dotted];\n");
					result.append(thisAbout);
					result.append('_');
					result.append(countNTmatchs - viewntmatchs);
					result.append("[shape=plaintext,style=solid,fontsize=10,label=\"");
					result.append(countNTmatchs-viewntmatchs);
					result.append("\"];\n");
				}
			}
		}

		if (inContext) {
			/*
			LinkedList<LinkedConcept> nms = aConcept.getNarrowMatch();
			if (nms != null && !nms.isEmpty()) {
				for (LinkedConcept anNT : nms) {
					result.append(thisAbout);
					result.append("->");
					result.append(anNT.getAbout());
					result.append("[arrowhead=odotnormal");
					result.append(Status.graphVizColor(anNT.getCurrentStatus()));
					result.append(Status.graphVizLabel(anNT.getRelationQualifier(),anNT.getCurrentStatus()));
					result.append("];\n");
					nodes.add(anNT.getAbout());
				}
			}*/
			LinkedList<LocalLinkedConcept> rts = aConcept.getRelated();
			if (rts != null && !rts.isEmpty()) {
				for (LocalLinkedConcept anRT : rts) {
					Concept rtConcept = aConcept.getInScheme().getConcept(anRT.getAbout());
					if (rtConcept != null) {
						result.append(thisAbout);
						result.append("->");
						result.append(rtConcept.getScheme_About());
						result.append("[arrowhead=none,style=dashed");
						result.append(Status.graphVizColor(anRT.getCurrentStatus()));
						result.append(Status.graphVizLabel(anRT.getRelationQualifier(),anRT.getCurrentStatus()));
						result.append("];\n");
						nodes.add(rtConcept.getScheme_About());
					}
				}
			}
			LinkedList<LinkedConcept> nms = aConcept.getRelatedMatch();
			if (nms != null && !nms.isEmpty()) {
				for (LinkedConcept anRT : nms) {
					result.append(thisAbout);
					result.append("->");
					result.append(anRT.getAbout());
					result.append("[arrowhead=odot,style=dashed");
					result.append(Status.graphVizColor(anRT.getCurrentStatus()));
					result.append(Status.graphVizLabel(anRT.getRelationQualifier(),anRT.getCurrentStatus()));
					result.append("];\n");
					nodes.add(anRT.getAbout());
				}
			}
			nms = aConcept.getExactMatch();
			if (nms != null && !nms.isEmpty()) {
				for (LinkedConcept eq : nms) {
					result.append(eq.getAbout());
					nodes.add(eq.getAbout());
					result.append("->");
					result.append(thisAbout);
					result.append("[dir=both,style=dashed,arrowtail=odotinv,arrowhead=inv");
					result.append(Status.graphVizColor(eq.getCurrentStatus()));
					result.append(Status.graphVizLabel(eq.getRelationQualifier(),eq.getCurrentStatus()));
					result.append("];\n");
				}
			}
			nms = aConcept.getCloseMatch();
			if (nms != null && !nms.isEmpty()) {
				for (LinkedConcept ceq : nms) {
					result.append(ceq.getAbout());
					nodes.add(ceq.getAbout());
					result.append("->");
					result.append(thisAbout);
					result.append("[dir=both,style=dashed,arrowtail=odotoinv,arrowhead=oinv");
					result.append(Status.graphVizColor(ceq.getCurrentStatus()));
					result.append(Status.graphVizLabel(ceq.getRelationQualifier(),ceq.getCurrentStatus()));
					result.append("];\n");
				}
			}
		}

		return result.toString();
	}

	public static String toDotHierarchy(SkosHtmlManager skosHtmlManager, SkosView skosView, LinkedList<String> nodes) {
		StringBuffer result = new StringBuffer();
		LinkedList<ViewConcept> tops = skosView.getNarrower();
		for (ViewConcept vcTop : tops) {
			//result.append(vcTop.getConcept().getInScheme().getAbout());
			//result.append("->");
			//result.append(vcTop.getConcept().getScheme_About());
			//result.append(";\n");
			result.append(toDotConceptHierarchy(skosHtmlManager, skosView, vcTop, nodes, !skosView.hasBroadContext()));
		}
		return result.toString();
	}

	public static String toDotLabels(SkosHtmlManager skosHtmlManager, SkosView skosView, LinkedList<String> nodes, int width, boolean includeNotes, boolean svg) {
		/*
		 * TODO:Links toward a source...
	jita_TH [label=<
		<table border="0" cellborder="0" cellspacing="0">
		  <tr>
		    <td href="/askosi/concept.jsp?concept=jita_TH&amp;graf=1#Graf" port="p1">A. Theoretical and<br/>general aspects of<br/>libraries and<br/>information</td>
		  </tr><tr>
		    <td href="/askosi/concept.jsp?concept=jita_TH&amp;expanded=yes" port="p2">(110 in E-Lis)</td>
		  </tr>
		</table>
	>]
		 */
		if (width <= 0) width	 = 24;
		SkosManager skosManager = skosHtmlManager.getSkosManager();
		String schemeCode = skosView.getAbout();
		String pivot = null;
		if (skosView.getPivot() != null) {
			pivot = schemeCode+SchemeInterface.separator+skosView.getPivot();
		}
		StringBuffer result = new StringBuffer();
		for (String aNode : nodes) {
			result.append(aNode);
			result.append("[");

			Concept aConcept = skosManager.getScheme_Concept(aNode);
			if (aConcept != null) {
				String conceptURL = null;
				result.append("URL=\"");
				conceptURL = aConcept.getUrlAdapted(skosHtmlManager);
				if (conceptURL != null && !conceptURL.isEmpty()) {
					result.append(conceptURL);
					
				} else {
					result.append(aConcept.getAskosiUrl(skosHtmlManager, "&amp;graf="+skosHtmlManager.getLanguage()+"#Graf"));					
				}
				result.append("\",label=\"");
				TermList labels = aConcept.getPrefLabel();
				String label = aConcept.getAbout();
				if (labels != null && !labels.isEmpty()) {
					label = labels.getLingTerm(aConcept.getInScheme().getSupraLanguage(), skosHtmlManager.getLanguage(), false).toGraphViz(width);
				}
				if (!aNode.startsWith(schemeCode+'_')) {
					label = label + "\\n(" + aConcept.getInScheme().getTitle().getLingTerm(aConcept.getInScheme().getSupraLanguage(), skosHtmlManager.getLanguage(), false).toGraphViz(width) + ')';
				}
				if (includeNotes) {
					TermList notes = aConcept.getScopeNote();
					String scopeNote = null;
					if (notes != null && ! notes.isEmpty()) {
						scopeNote = notes.getLingTerm(aConcept.getInScheme().getSupraLanguage(), skosHtmlManager.getLanguage(), false).toGraphViz(width);
						if (scopeNote != null && !scopeNote.isEmpty()) {
							label = label + "\\n[" + scopeNote + ']';
						}
					}
				}
				result.append(Util.escapeDot(label));
				result.append("\",");
			}
			if (aNode.equals(pivot)) {
				result.append("style=filled,fillcolor=yellow,");
			}
			result.append("tooltip=\"");
			result.append(aNode);
			result.append("\",id=\"");
			result.append(aNode);
			result.append("\"");
			if (svg) {
				result.append(",target=\"_parent\"");
			}
			result.append("];\n");
		}
		return result.toString();
	}
}
